home *** CD-ROM | disk | FTP | other *** search
Text File | 1993-06-18 | 23.1 KB | 591 lines | [TEXT/MPS ] |
- AppWannabe QuickStart:
-
- Step 1 is to build the libraries. If you have not built the libraries, please see either the
- “==MPW read me” or “==THINK read me” file.
-
- Getting started with the tutorial:
-
- Step 1) Copy the AppWannabe project folder.
- Step 2) If you are using MPW, you may wish to delete the files “AppWannabe.π” and
- “AppWannabe.π.rsrc”. If you are using THINK, you may wish to delete the
- file “AppWannabe.make”.
- Step 3) You may wish to change the name of the project from AppWannabe. Change the file name
- for those files names AppWannabe-something. This tutorial will assume that you have
- left the project names AppWannabe.
- Step 4) Build AppWannabe. If you changed the project name, and you are using MPW, you will have
- to change the “AppName” in the make file.
- Step 5) Run AppWannabe. Note that the “File” and “Edit” menus are already implemented.
- (The rest of the menus are your problem.)
-
- So, you now have the standard AppWannabe running. Let's change it.
-
- The most noticeable change you can make is to get something to draw in the window. The code
- for drawing in the window is in the file “Window.c”.
-
- So, let's open the source file “Window.c” and take a look. A quick way to see what this
- source file is about is to search for •• marks. The functions that have •• in the comments
- are automatically called by the library's framework code. All you need to do is to drop
- some code into these functions, and the framework calls it at the right times.
-
- Sounds good. Does it work? Let's try it.
-
- Find the function “ImageDocument” and add the following code to it:
-
- Rect rct;
-
- SetRect(&rct, 10, 10, 300, 200);
- FrameRect(&rct);
-
- The code that's already there draws any controls that may have been added to the AppWannabe
- application using AppsToGo. You'll want to leave that code there.
-
- ImageDocument() should now look like this:
-
- OSErr ImageDocument(FileRecHndl frHndl)
- {
- #pragma unused (frHndl)
-
- WindowPtr curPort;
- Rect rct;
-
- SetRect(&rct, 10, 10, 300, 200);
- FrameRect(&rct);
-
- GetPort(&curPort);
- if (!gPrintPage) { /* If not printing... */
- DoDrawControls(curPort, false); /* Draw the content controls. */
- }
- gPrintPage = 0;
-
- return(noErr);
- }
-
- Build it and run it. A simple rect shows up in the window. We can note some interesting
- behaviors at this point. Note that the zoom box zooms to the size that the window already
- is. That is because the window is zoomed to the document size. The default document size
- is the size of the window based on the 'WIND' resource for the window (number 128).
-
- We can change the initial window size in two ways:
-
- 1) We can change the resource 'WIND', ID#128.
- 2) We can change the code to override the default document size.
-
- Method 1 doesn't really change anything. The window will open a different size, but
- the zoom box will still zoom to the same (new) size, as the default document size is
- still the 'WIND' resource size. So let's do method 2.
-
- At the point in the document where ImageDocument is called, it is too late to change the
- size of the window. You could, but the window is already being displayed, so changing it
- here would be ugly. However, we could change the document size at this point. Even though
- ImageDocument() isn't the place to do this, let's change it there anyway.
-
- There is a library call to change the document size. It records the new size, plus it
- adjusts the scrollbar max values to reflect that more area can now be scrolled to.
- Here's the prototype for the call:
-
- void SetDocSize(FileRecHndl frHndl, long hSize, long vSize);
-
- Just pass in the file record handle (frHndl), and the new horizontal and vertical size
- of the document.
-
- ImageDocument() is passed in the frHndl of the document to be imaged, so all we have to do
- is to pass it to SetDocSize().
-
- NOTE: For MPW users, passing frHndl to SetDocSize() means that frHndl is no longer not used.
- You probably want to remove the #pragma unused when you add this code.
-
-
- So we would add the following line to ImageDocument():
-
- SetDocSize(frHndl, 2000, 2000); /* Make document plenty big. */
-
-
- ImageDocument() would now look like this:
-
- OSErr ImageDocument(FileRecHndl frHndl)
- {
- WindowPtr curPort;
- Rect rct;
-
- SetDocSize(frHndl, 2000, 2000); /* Make document plenty big. */
-
- SetRect(&rct, 10, 10, 300, 200);
- FrameRect(&rct);
-
- GetPort(&curPort);
- if (!gPrintPage) { /* If not printing... */
- DoDrawControls(curPort, false); /* Draw the content controls. */
- }
- gPrintPage = 0;
-
- return(noErr);
- }
-
-
- Now the zoom actually changes the size of the window. However, it becomes apparent that
- there is something wrong when the window is zoomed, compared to how the window initially
- comes up. When the window initially comes up, the scrollbars are still inactive, but
- when the window is zoomed, they become active. They should be active all of the time, since
- we set the document size substantially greater than the viewable part of the window.
-
- So, what's the deal?
-
- The deal is that the scrollbars are active when the window initially comes up. They just
- don't look it. Try creating a new window and then start scrolling right away. The
- scrollbars do actually work. Well, this is a bug, isn't it?
-
- Yep. It's a bug. The bug is that SetDocSize() didn't redisplay the scrollbars when we
- called it. Actually SetDocSize is fine. The problem is that the scrollbars are clipped
- out of the drawing area when we are in ImageDocument(). This is actually good, as we
- don't want the content of the window drawing over our scrollbars. The library first
- removes the frame area from the drawable portion of a window, and then the library calls
- ImageDocument().
-
- What this means is that the SetDocSize() call doesn't belong in the ImageDocument() function.
- (I said it wasn't a good place for it.) Well, where does it belong? You could place the
- call in the function InitContent(). InitContent() is called after the window is created,
- but before it is displayed, and you now want to initialize the content of the window.
- Moving the SetDocSize() call to here will make this work quite nicely. Try it. Put the
- ImageDocument() function back to the way it was prior to our adding the SetDocSize call to
- it, and place the SetDocSize() call in the “Window.c” function InitContent(). (MPW users
- have to contend with “#pragma unused” statements.) The InitContent() function would look
- like this:
-
- OSErr InitContent(FileRecHndl frHndl, WindowPtr window)
- {
- OSErr err;
-
- SetDocSize(frHndl, 2000, 2000);
-
- err = AddControlSet(window, (*frHndl)->fileState.sfType, kwStandardVis, 0, 0, nil);
- return(err);
- }
-
- The AddControlSet() call is so that controls added in AppsToGo will be added to the
- content of the window, so calling AddControlSet() here is correct.
-
- Now the scrollbars appear initially correct. Note that the initial window size hasn't
- changed. As a point of clarification, I should state that InitContent() is called only
- once per window. It is only called when a window is getting created. ImageDocument()
- gets called every time the window needs redrawing (or when the user is printing, but
- more on this later).
-
- What if we wanted the window to initially open to reflect the size of the document? Of
- course a document as large as 2000,2000 isn't going to fit on many monitors, but how about
- trying to open the document this big, not to exceed the size of the monitor? What this
- means is setting the document size from within the InitContent() function is too late.
- The window has already been created and sized, and InitContent() is being called to make
- changes to the content of a window, not the window itself. We could also resize the window,
- as it hasn't been displayed yet, but that's not the best thing to do. The window creation
- function in DTS.framework went to a lot of trouble to position the window base on its size.
- Changing the size now would cause us more work than we want at this point. (DTS.framework
- staggers and auto-sizes windows, plus it leaves a pleasant border around the window. We
- want to utilize all of this behavior.)
-
- So, if InitContent() is too late, where isn't it too late?
-
- Here's how you work with documents in this framework. You first try to create a document,
- (an frHndl), and if you succeed, you then give the document a window. You can do stuff
- between creating a document and giving the document a window. The user doesn't see the
- actual document. They just see the window you give the document, so until you give the
- document a window, the user will see nothing. This is where you would want to set the
- document size, between document creation and window creation.
-
- Since you want to set the document size prior to having a window, it is reasonable to
- expect that you will have to add code to some file other than the “Window.c” file.
- Again, this is the case. You want to add code to the file “File.c”. The function that
- you want to add code to is called InitDocument.
-
- InitDocument() looks like this:
-
-
- OSErr InitDocument(FileRecHndl frHndl)
- {
- OSErr err;
-
- err = noErr;
-
- switch ((*frHndl)->fileState.sfType) {
- case kDocFileType:
- err = DefaultInitDocument(frHndl, kVersion, kMaxNumUndos, kNumSaveUndos);
- if (!err) {
- /* Any additional document initialization could go here. */
- }
- break;
- #if VH_VERSION
- case kViewHierFileType:
- return(VHInitDocument(frHndl));
- break;
- #endif
- default:
- err = DefaultInitDocument(frHndl, kVersion, kMaxNumUndos, kNumSaveUndos);
- if (!err) {
- (*frHndl)->fileState.readDocumentProc = nil;
- (*frHndl)->fileState.writeDocumentProc = nil;
- }
- break;
- }
-
- return(err);
- }
-
-
- InitDocument() is given a file reference (frHndl). The frHndl contains a bunch of file
- information, one part of which is the file type. The switch statement gets this type
- from the frHndl and then the case statements do the appropriate thing based on that type.
-
- You don't have to worry about the kViewHierFileType case statement. That is there so that
- the View Hierarchy debugging facility is available. Discarding this file type, there is
- only one file type left, kDocFileType. If you have only one type of document in your
- application, this is all you will ever need. If you add document types to your application,
- you will end up with more case statements here.
-
- You want to replace the comment “Any additional document...” with your code, so that the
- function looks like this:
-
- OSErr InitDocument(FileRecHndl frHndl)
- {
- OSErr err;
-
- err = noErr;
-
- switch ((*frHndl)->fileState.sfType) {
- case kDocFileType:
- err = DefaultInitDocument(frHndl, kVersion, kMaxNumUndos, kNumSaveUndos);
- if (!err) {
- SetDocSize(frHndl, 2000, 2000);
- }
- break;
- #if VH_VERSION
- case kViewHierFileType:
- return(VHInitDocument(frHndl));
- break;
- #endif
- default:
- err = DefaultInitDocument(frHndl, kVersion, kMaxNumUndos, kNumSaveUndos);
- if (!err) {
- (*frHndl)->fileState.readDocumentProc = nil;
- (*frHndl)->fileState.writeDocumentProc = nil;
- }
- break;
- }
-
- return(err);
- }
-
-
- Don't forget to yank the SetDocSize() call out of InitContent(), as we have taken care of
- setting the document size elsewhere and don't need it anymore. Run it and test it out.
-
-
- So that was reasonably painless. What else can we easily do?
-
- How about adding a ruler to our window?
-
- Since we are in the function InitDocument(), let's set another document attribute. Let's set
- the topSidebar to a non-zero value and see what happens. Add the following lines below the
- SetDocSize() call:
-
- SetSidebarSize(frHndl, kwNoChange, 40);
- /* Don't change the left sidebar (leave it 0),
- ** but set the top sidebar to 40. */
-
- Now when you run the application, you see that the vertical scrollbar doesn't go all the
- way to the top anymore. There's a 40 pixel gap at the top of the window. Try scrolling
- the rect around the window. Note that it is clipped out of this area. This area shouldn't
- scroll with with document, as we want to use it as a ruler. All we need to do now is to
- write some code to draw the ruler.
-
- So, where does our ruler drawing code go? It goes in a function called DrawFrame().
- DrawFrame() is found in the “Window.c” file. It is also called automatically by the
- framework when the frame needs redrawing. I consider it part of the frame, just like
- the scrollbars and grow icon. The frame portion of the window can't be drawn over by
- the ImageDocument procedure. This is why the rect gets scrolled behind this top area.
- The framework sets the origin to -16384,0 prior to calling DrawFrame. The frame
- origin is offset by -16384 horizontally so that controls in the frame area can be
- drawn simply by calling DoDrawControls(). Controls with an origin offset -16384
- horizontally will be drawn, and all other controls will be pushed out of the window
- and therefore will not display in the frame.
-
- Given that the origin is -16384,0, here's some quick-and-ugly ruler code that we can use:
-
- short i;
-
- for (i = 0; i < 16; ++i) {
- MoveTo(72 * i - 16384, 0);
- Line(0, 16); /* Draw inch marks. */
-
- MoveTo(72 * i + 36 - 16384, 0); /* Draw 1/2 inch marks. */
- Line(0, 8);
-
- MoveTo(72 * i + 18 - 16384, 0); /* Draw 1/4 inch marks. */
- Line(0, 4);
- MoveTo(72 * i + 54 - 16384, 0);
- Line(0, 4);
- }
-
- Add it to the DrawFrame() function “Window.c”. The code that's already there draws lines
- so the edge of the frame can be seen. Also, the code:
-
- BeginFrame(window);
- DoDrawControls(window, activate);
- EndFrame(window);
-
- draws any controls that were added to the frame using AppsToGo, so leave that there, too.
-
-
- After adding the above code to DrawFrame(), it looks like this:
-
- void DrawFrame(FileRecHndl frHndl, WindowPtr window, Boolean activate)
- {
- short i;
-
- for (i = 0; i < 16; ++i) {
- MoveTo(72 * i - 16384, 0);
- Line(0, 16); /* Draw inch marks. */
-
- MoveTo(72 * i + 36 - 16384, 0); /* Draw 1/2 inch marks. */
- Line(0, 8);
-
- MoveTo(72 * i + 18 - 16384, 0); /* Draw 1/4 inch marks. */
- Line(0, 4);
- MoveTo(72 * i + 54 - 16384, 0);
- Line(0, 4);
- }
-
- MoveTo(0, (*frHndl)->fileState.topSidebar - 1);
- LineTo((*frHndl)->fileState.leftSidebar - 1 - 16384, (*frHndl)->fileState.topSidebar - 1);
- LineTo((*frHndl)->fileState.leftSidebar - 1 - 16384, 16383);
-
- BeginFrame(window);
- DoDrawControls(window, activate);
- EndFrame(window);
- }
-
-
- We could add text to show the inches and stuff, but this is just a tutorial, so the
- heck with it.
-
- Now, let's run the application and note a problem. If we scroll vertically, everything works
- just fine, but if we scroll horizontally, we should really scroll the ruler, too.
-
- No problem. The framework calls us at the right time. The function ScrollFrame() is called
- when the document is scrolled. Often you will do nothing, as you may not even have rulers
- in your document, but here we do care.
-
- There are a couple of ways we could address this. One is to use ScrollRect() to scroll the
- ruler the right amount, and then call DrawFrame(). Here's some code to do this:
-
- void ScrollFrame(FileRecHndl frHndl, WindowPtr window, long dh, long dv)
- {
- WindowPtr oldPort;
- Rect rct;
- RgnHandle rgn;
-
- GetPort(&oldPort);
- SetPort(window);
- SetOrigin(0, 0);
-
- rct = window->portRect; /* Get the window's portRect, so we can scroll the top (ruler). */
- rct.bottom = 39; /* Not 40, as there is no need to scroll the bottom line. */
- rgn = NewRgn();
- ScrollRect(&rct, dh, 0, rgn); /* Only scroll horizontally. */
- DisposeRgn(rgn);
- DrawFrame(frHndl, window, ((WindowPeek)window)->hilited);
-
- SetPort(oldPort);
- }
-
- Try it. It works pretty crummy, huh?
-
- The only problem is that the code we wrote for DrawFrame() assumed an origin of -16384,0.
- Since the ruler can now scroll, we have to take this into account. Here's the new DrawFrame()
- code that takes the horizontal origin into account:
-
- void DrawFrame(FileRecHndl frHndl, WindowPtr window, Boolean activate)
- {
- short i;
- Point org;
-
- GetContentOrigin(window, &org);
- SetOrigin(org.h, 0);
-
- for (i = 0; i < 16; ++i) {
- MoveTo(72 * i, 0);
- Line(0, 16); /* Draw inch marks. */
-
- MoveTo(72 * i + 36, 0); /* Draw 1/2 inch marks. */
- Line(0, 8);
-
- MoveTo(72 * i + 18, 0); /* Draw 1/4 inch marks. */
- Line(0, 4);
- MoveTo(72 * i + 54, 0);
- Line(0, 4);
- }
-
- SetOrigin(0, 0);
- MoveTo(16383, (*frHndl)->fileState.topSidebar - 1);
- LineTo((*frHndl)->fileState.leftSidebar - 1, (*frHndl)->fileState.topSidebar - 1);
- LineTo((*frHndl)->fileState.leftSidebar - 1, 0);
-
- BeginFrame(window); /* BeginFrame resets the origin to -16384,0. */
- DoDrawControls(window, activate);
- EndFrame(window);
- }
-
-
- So that covers how to scroll the ruler using the toolbox call ScrollRect. Here's a way to
- scroll the ruler without calling ScrollRect. We first change ScrollFrame to simply
- call DrawFrame, and then we let DrawFrame do all the work.
-
- We can use the offscreen drawing package GWLayers to first draw the ruler offscreen, and
- then transfer it to the window. Here's the DrawFrame code to do this:
-
-
- void DrawFrame(FileRecHndl frHndl, WindowPtr window, Boolean activate)
- {
- WindowPtr oldPort;
- Point oldOrg, contOrg;
- LayerObj windowLayer, rulerLayer;
- Rect rct;
- short i;
-
- GetPort(&oldPort); /* The framework sets the port for us, and it sets */
- SetPort(window); /* the origin to -16384,0, but now ScrollFrame can */
- oldOrg.h = window->portRect.left; /* call here, so do the port saving and origin stuff, */
- oldOrg.v = window->portRect.top; /* since we don't know where we were called from. The */
- /* upper-left of the window's portRect is the origin, */
- /* so by saving those, we will be able to set the */
- /* origin back to whatever it was before. */
-
- GetContentOrigin(window, &contOrg); /* Find out where the document is scrolled. */
- SetOrigin(contOrg.h, 0); /* Set the window's horizontal origin to that. */
-
- NewLayer(&windowLayer, nil, nil, window, 0, 0L); /* We are using GWLayers to draw */
- rct = window->portRect; /* offscreen. The NewLayer call */
- rct.bottom = 39; /* created a layer object for the */
- (*windowLayer)->dstRect = rct; /* whole window. We only want the */
- /* top 39 pixels, so set dstRect */
- /* to the top part of the window. */
-
- NewLayer(&rulerLayer, windowLayer, nil, nil, 0, 0L);
- SetLayerWorld(rulerLayer);
- /* We may not have enough memory to create the offscreen GWorld, so this call
- ** may fail, but that's actually okay. If NewLayer fails, it returns nil for
- ** rulerLayer. If we pass nil into SetLayerWorld, it does nothing. If it does
- ** nothing, then the port is still set to the last port, which is the document's
- ** window (SetPort above). This means that in the worst case, the ruler will
- ** flicker when it is redrawn. We will not blow up. */
-
- EraseRect(&rct);
- /* Erase the offscreen GWorld (or possibly the ruler in the window if low on ram). */
-
- for (i = 0; i < 16; ++i) {
- MoveTo(72 * i, 0);
- Line(0, 16); /* Draw inch marks. */
-
- MoveTo(72 * i + 36, 0); /* Draw 1/2 inch marks. */
- Line(0, 8);
-
- MoveTo(72 * i + 18, 0); /* Draw 1/4 inch marks. */
- Line(0, 4);
- MoveTo(72 * i + 54, 0);
- Line(0, 4);
- }
-
- InvalLayer(windowLayer, GetEffectiveDstRect(windowLayer), false);
- /* Invalidate the entire layer so it all transfers to the window. */
-
- UpdateLayer(windowLayer); /* Transfer all invalid areas. */
- ResetLayerWorld(rulerLayer); /* Undo the above SetLayerWorld. */
- DisposeThisAndBelowLayers(windowLayer); /* Dispose of all the layers we created */
- /* in this function. */
-
- SetOrigin(0, 0);
- MoveTo(16383, (*frHndl)->fileState.topSidebar - 1);
- LineTo((*frHndl)->fileState.leftSidebar - 1, (*frHndl)->fileState.topSidebar - 1);
- LineTo((*frHndl)->fileState.leftSidebar - 1, 16383);
-
- BeginFrame(window); /* BeginFrame resets the origin to -16384,0. */
- DoDrawControls(window, activate);
- EndFrame(window);
-
- SetOrigin(oldOrg.h, oldOrg.v); /* Put origin and port back the way they were. */
- SetPort(oldPort);
- }
-
-
- And ScrollFrame() looks like this:
-
- void ScrollFrame(FileRecHndl frHndl, WindowPtr window, long dh, long dv)
- {
- #pragma unused (dh, dv)
-
- DrawFrame(frHndl, window, ((WindowPeek)window)->hilited);
- }
-
-
- The first method seems simpler, but for certain types of rulers, you may wish to use this
- technique. The first method first does a ScrollRect(), and then it redraws. The
- ScrollRect() erases the area that scrolled into the window. It then gets redrawn via
- calling DrawFrame(). This means that for an instant part of the ruler is white. If the
- drawing of the ruler is fast, then this is no big deal. If however, your ruler is
- somewhat complicated and takes a while to draw, then you might want to use this second
- technique.
-
- This second method was also a good way to introduce you to the GWLayers code. Note that
- GWLayers also works for system 6, even if the GWorld calls aren't available. There is
- no system 6 v.s. system 7 compatibility hit if you use GWLayers.
-
-
- Another nice feature of the second sample is that DrawFrame() is robust. It doesn't depend
- on the framework setting up things nicely (setting the port, setting the origin).
- It handles these details, which allows you to call it from less polite code, such as the
- ScrollFrame() procedure for this method.
-
- So, DrawFrame() is very robust, and ScrollFrame() is really stupid. What could be better?
-
- The above code isn't very object-oriented. If your application has only one document type,
- then there's nothing wrong with the above code. However, if you have more than one document
- type, then you should code ScrollFrame() so that it uses whatever DrawFrame() procedure the
- document has set.
-
- You probably have been wondering how the framework knows what function to call. In effect,
- it doesn't. A pointer to the appropriate function is stored in the frHndl for each of these
- functions. You can change any of these function pointers. Let's say that you create a
- second document type that has a different ruler. Possibly the other document type has a
- vertical ruler, instead of the horizontal ruler we use in this example. This second
- document would have a different pointer for its DrawFrame() function. However, it could
- have the same pointer for the ScrollFrame() function. Given this possibility, here's what
- should be done in ScrollFrame():
-
-
-
- void ScrollFrame(FileRecHndl frHndl, WindowPtr window, long dh, long dv)
- {
- #pragma unused (dh, dv)
-
- DrawFrameProcPtr proc;
-
- if (proc = (*frHndl)->fileState.drawFrameProc)
- (*proc)(frHndl, window, ((WindowPeek)window)->hilited);
- }
-
-
- Unless the DrawFrame() procedure pointer is changed, the above code does exactly the same
- thing. When a document is created, the application framework sets the procedure pointers
- to initial values. The initial values are the functions found in the “Window.c” file. If
- you have only one document type, then you can leave these pointers just as they are and
- simply drop code into the appropriate functions in “Window.c”, just as we have been doing
- so far. However, if you have multiple document types in your application, you are most
- likely going to want to change some of these procedure pointers.
-
- With this flexibility comes the responsibility of not making direct calls. We need to look
- up the procedure pointer that the document wants used, and then call that procedure.
- An additional feature of the framework is that if the procedure pointer is nil, then that
- particular facility isn't used. This allows you to turn off any of the features of a
- document simply by setting the procedure pointer to nil. So, before you call one of these
- procedures, you first get the procedure pointer, and then if it isn't nil, you call it.
-
-
-